﻿
namespace TingSLsocket

open System
open System.Collections.Concurrent
open System.Net.Sockets

type ServerJobAgent = JobAgent

[<Sealed>]
[<AllowNullLiteral>]
type RpcServer(numChannel0,perBufSize,_agentFactory : Guid -> ServerJobAgent)=
    let _tcpserver = new TcpServer(numChannel0,perBufSize)
    let _agents = new ConcurrentDictionary<Guid,ServerJobAgent>()

    let _evtClientConnected = new Event<Guid>()
    let _evtClientDisconnected = new Event<Guid>()
    let _evtClientError = new Event<Guid*exn>()

    let onClientConnected clientId =
        let agent = _agentFactory clientId
        if _agents.TryAdd(clientId,agent) then
            agent.ForwardEvt.Add (fun (clientId,buf) -> _tcpserver.SendAsync(clientId,buf,0,buf.Length))
            agent.ErrorEvt.Add _evtClientError.Trigger
            _evtClientConnected.Trigger clientId
        else
            (agent :> IDisposable).Dispose()
            failwith "failed to add Agent for client"

    let onClientDisconnected clientId =
        let success,agent = _agents.TryRemove clientId
        if success then
            (agent :> IDisposable).Dispose()
            _evtClientDisconnected.Trigger clientId

    let onClientDataReceived (clientId,buffer,offset,size) =
        let success,agent = _agents.TryGetValue clientId
        if success then
            agent.Receive(buffer,offset,size)

    do
        _tcpserver.ClientConnectedEvt.Add onClientConnected
        _tcpserver.ClientDisconnectedEvt.Add onClientDisconnected
        _tcpserver.DataRecvExposeEvt.Add onClientDataReceived
        _tcpserver.ClientErrorEvt.Add (fun (id,errcode) -> 
                                                        let sockerr = new SocketException(int errcode) 
                                                        _evtClientError.Trigger (id,sockerr :> exn) )

    [<CLIEvent>]
    member this.ClientConnectedEvt = _evtClientConnected.Publish

    [<CLIEvent>]
    member this.ClientDisconnectedEvt = _evtClientDisconnected.Publish

    [<CLIEvent>]
    member this.ClientErrorEvt = _evtClientError.Publish

    member this.Start(lsnport,?backlog) =
        match backlog with
        | Some(n) -> _tcpserver.Start(lsnport,n)
        | None -> _tcpserver.Start(lsnport)

    member this.Request (clientId,cmd,parm) =
        let success,agent = _agents.TryGetValue clientId
        if success then
            agent.Request(cmd,parm)

    member this.RequestAll (cmd,parm) =
        _agents
        |> Seq.iter (fun kv ->kv.Value.Request(cmd,parm))

    interface IDisposable with
        member this.Dispose() =
            (_tcpserver :> IDisposable).Dispose()
            _agents
            |> Seq.iter (fun kv -> (kv.Value :> IDisposable).Dispose())

